package com.milhouz.wormdroid.gl.renderer;

import java.util.ArrayList;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.milhouz.wormdroid.gl.model.GLSprite;
import com.milhouz.wormdroid.gl.model.hud.IHudSprite;
import com.milhouz.wormdroid.gl.util.Grid;
import com.milhouz.wormdroid.gl.view.GameGLSurfaceView;

/**
 * An OpenGL ES renderer based on the GLSurfaceView rendering framework. This class is responsible for drawing a list of renderables to the screen every frame. It also manages loading of textures and
 * (when VBOs are used) the allocation of vertex buffer objects.
 */
public class GameGLRenderer implements GameGLSurfaceView.Renderer {
	private final static float ZOOM_FACTOR_0 = 1f;
	private final static float ZOOM_FACTOR_1 = 0.75f;
	private final static float ZOOM_FACTOR_2 = 0.5f;

	// Specifies the format our textures should be converted to upon load.
	private static BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
	// An array of things to draw every frame.
	private ArrayList<GLSprite> mSprites;
	private ArrayList<IHudSprite> mHudSprites;
	// Pre-allocated arrays to use at runtime
	//private int[] mTextureNameWorkspace;
	//private int[] mCropWorkspace;
	// A reference to the application context.
	private Context mContext;
	// Determines the use of vertex buffer objects.
	private boolean mUseHardwareBuffers = true;

	public int zx;
	public int zy;
	private int screenWidth;
	private int screenHeight;
	public boolean isZoomed = false;
	public boolean showHud = true;
	private float zoomFactor = ZOOM_FACTOR_0;

	public GameGLRenderer(Context context) {
		// Pre-allocate and store these objects so we can use them at runtime
		// without allocating memory mid-frame.
		//mTextureNameWorkspace = new int[1];
		//mCropWorkspace = new int[4];

		// Set our bitmaps to 16-bit, 565 format.
		sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
		mContext = context;
	}

	public int[] getConfigSpec() {
		// We don't need a depth buffer, and don't care about our
		// color depth.
		int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_NONE };
		return configSpec;
	}

	public void setSprites(ArrayList<GLSprite> sprites) {
		mSprites = sprites;
	}

	public void setHudSprites(ArrayList<IHudSprite> sprites) {
		mHudSprites = sprites;
	}

	public void removeSprite(GLSprite sprite) {
		if (mSprites != null && sprite != null) {
			mSprites.remove(sprite);
		}
	}

	/*
	 * Changes the vertex mode used for drawing.
	 * 
	 * @param useVerts Specifies whether to use a vertex array. If false, the DrawTexture extension is used.
	 * 
	 * @param useHardwareBuffers Specifies whether to store vertex arrays in main memory or on the graphics card. Ignored if useVerts is false.
	 * 
	 * public void setVertMode(boolean useVerts, boolean useHardwareBuffers) { mUseVerts = useVerts; mUseHardwareBuffers = useVerts ? useHardwareBuffers : false; }
	 */

	public void drawFrame(GL10 gl, int width, int height) {
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		drawPhysicSprites(gl, width, height);
		if (showHud)
			drawTextSprites(gl, width, height);
	}

	/** Draws the sprites. */
	public void drawPhysicSprites(GL10 gl, int width, int height) {
		Grid.beginDrawing(gl, true);
		if (mSprites != null)
			for (int x = 0; x < mSprites.size(); x++) {
				mSprites.get(x).draw(gl, zx, zy, isZoomed, zoomFactor);
			}
		Grid.endDrawing(gl);
	}

	/** Draws the texts. */
	public void drawTextSprites(GL10 gl, int width, int height) {
		if (mHudSprites != null)
			for (IHudSprite hudSprite : mHudSprites) {
				hudSprite.draw(gl, width, height);
			}
	}

	/* Called when the size of the window changes. */
	public void sizeChanged(GL10 gl, int width, int height) {
		gl.glViewport(0, 0, width, height);
		screenWidth = width;
		screenHeight = height;
		/*
		 * Set our projection matrix. This doesn't have to be done each time we draw, but usually a new projection needs to be set when the viewport is resized.
		 */
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrthof(0.0f, width, 0.0f, height, 0f, 1.5f);
		gl.glShadeModel(GL10.GL_FLAT);
		gl.glEnable(GL10.GL_BLEND);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);
		gl.glEnable(GL10.GL_TEXTURE_2D);
	}

	/**
	 * Called whenever the surface is created. This happens at startup, and may be called again at runtime if the device context is lost (the screen goes to sleep, etc). This function must fill the
	 * contents of vram with texture data and (when using VBOs) hardware vertex arrays.
	 */
	public void surfaceCreated(GL10 gl) {
		/*
		 * Some one-time OpenGL initialization can be made here probably based on features of this particular context
		 */
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

		gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
		gl.glShadeModel(GL10.GL_FLAT); // GL_SMOOTH ????
		gl.glDisable(GL10.GL_DEPTH_TEST);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		/*
		 * By default, OpenGL enables features that improve quality but reduce performance. One might want to tweak that especially on software renderer.
		 */
		gl.glDisable(GL10.GL_DITHER);
		gl.glDisable(GL10.GL_LIGHTING);

		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		// Initialize Sprites Textures
		if (mSprites != null) {
			for (GLSprite sprite : mSprites) {
				sprite.setTextureName(sprite.loadTextureBitmap(mContext, gl));
			}
		}

		// Initialize Textual sprites
		if (mHudSprites != null)
			for (IHudSprite hudSprite : mHudSprites) {
				hudSprite.initialize(gl);
			}	
	}
	
	public void addAndLoadSprite(GL10 gl, GLSprite sprite) {
		if (sprite != null) {
			if (mSprites == null)
				mSprites = new ArrayList<GLSprite>();
			sprite.setTextureName(sprite.loadTextureBitmap(mContext, gl));
			mSprites.add(sprite);
		}
	}

	/**
	 * Called when the rendering thread shuts down. This is a good place to release OpenGL ES resources.
	 * 
	 * @param gl
	 */
	public void shutdown(GL10 gl) {
		if (mSprites != null) {

			int lastFreedResource = -1;
			int[] textureToDelete = new int[1];

			for (int x = 0; x < mSprites.size(); x++) {
				int resource = mSprites.get(x).getResourceId();
				if (resource != lastFreedResource) {
					textureToDelete[0] = mSprites.get(x).getTextureName();
					gl.glDeleteTextures(1, textureToDelete, 0);
					mSprites.get(x).setTextureName(0);
				}
				if (mUseHardwareBuffers) {
					mSprites.get(x).getGrid().freeHardwareBuffers(gl);
				}
			}
		}

		// Shutdown Hud Sprites
		if (mHudSprites != null)
			for (IHudSprite hudSprite : mHudSprites) {
					hudSprite.shutdown(gl);
			}	
	}


	public void moveCamera(int dx, int dy) {
		zx = dx;
		zy = dy;
	}

	public void moveCameraWithSprite(GLSprite sprite) {
		if (sprite != null && sprite.getVelocity() != null && sprite.isViewCentered) {
			if (sprite.getVelocity().x != 0 || sprite.getVelocity().y != 0) {
				centerCameraOnSprite(sprite);
			}
		}
	}

	public void centerCameraOnSprite(GLSprite sprite) {
		if (sprite != null && isZoomed) {
			zx = (int) (((screenWidth / 2) - sprite.getX()) * zoomFactor);
			zy = -(int) (((screenHeight / 2) - sprite.getY()) * zoomFactor);
		} else if (sprite != null) {
			zx = (int) ((screenWidth / 2) - sprite.getX());
			zy = -(int) ((screenHeight / 2) - sprite.getY());
		}
	}

	public void setScreenDimension(int width, int height) {
		this.screenHeight = height;
		this.screenWidth = width;
	}

	public ArrayList<GLSprite> getSprites() {
		return mSprites;
	}
	
	public GLSprite getSpriteById(int idSprite) {
		for (GLSprite sprite : mSprites) {
			if (sprite.id == idSprite) {
				return sprite;
			}
		}
		return null;
	}

	public void setZoom() {
		if (!isZoomed) {
			zoomFactor = ZOOM_FACTOR_1;
			isZoomed = !isZoomed;
		} else if (isZoomed && zoomFactor == ZOOM_FACTOR_1) {
			zoomFactor = ZOOM_FACTOR_2;
		} else if (isZoomed && zoomFactor == ZOOM_FACTOR_2) {
			zoomFactor = ZOOM_FACTOR_0;
			isZoomed = !isZoomed;
		}
	}

	public boolean isZoomed() {
		return isZoomed;
	}

}
